package com.ben.multiple.util;

import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

/**
 * com.baomidou.mybatisplus.core.toolkit.Sequence
 *
 * @author BenJin Yin
 * @date 2020/10/29
 */
public final class SnowFlakeId {
    private static final long DATA_CENTER_ID_BITS = 5L;
    private static final long WORKER_ID_BITS = 5L;
    private static final long SEQUENCE_BITS = 12L;
    private static final long MAX_DATA_CENTER_ID = (1 << DATA_CENTER_ID_BITS) - 1L;
    private static final long MAX_WORKER_ID = (1 << WORKER_ID_BITS) - 1L;
    private static final long MAX_SEQUENCE = (1 << SEQUENCE_BITS) - 1L;
    private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;
    private static final long DATA_CENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
    private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATA_CENTER_ID_BITS;
    private static final long BASE_TIMESTAMP = LocalDate.of(2020, 10, 1)
            .atStartOfDay(ZoneId.systemDefault())
            .toInstant().toEpochMilli();


    private final int workerId;
    private final int dataCenterId;
    private int sequence;
    private long lastTimestamp;

    public SnowFlakeId(int workerId, int dataCenterId) {
        if (workerId > MAX_WORKER_ID || workerId < 0) {
            throw new RuntimeException("workerId out of range");
        }
        if (dataCenterId > MAX_DATA_CENTER_ID || dataCenterId < 0) {
            throw new RuntimeException("dataCenterId out of range");
        }
        this.workerId = workerId;
        this.dataCenterId = dataCenterId;
        sequence = 0;
        lastTimestamp = -1L;
    }

    public synchronized long nextId() {
        long millis = System.currentTimeMillis();
        if (millis < lastTimestamp) {
            long offset = lastTimestamp - millis;
            if (offset <= 5) {
                try {
                    wait(offset << 1);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                millis = System.currentTimeMillis();
                if (millis < lastTimestamp) {
                    throw new RuntimeException("time error");
                }
            } else {
                throw new RuntimeException("time error");
            }
        }
        // 同一时刻，序列号递增；序列号到达最大值，获取下一个时间，序列号重置。
        if (millis == lastTimestamp) {
            ++sequence;
            if (sequence > MAX_SEQUENCE) {
                sequence = 0;
                while (millis == lastTimestamp) {
                    millis = System.currentTimeMillis();
                }
            }
        } else {
            // 不同时刻，序列号取值 0；
            sequence = 0;
        }
        lastTimestamp = millis;
        return ((millis - BASE_TIMESTAMP) << TIMESTAMP_SHIFT)
                | (dataCenterId << DATA_CENTER_ID_SHIFT)
                | (workerId << WORKER_ID_SHIFT)
                | sequence;
    }


    public static void main(String[] args) {
        SnowFlakeId snowFlakeId = new SnowFlakeId(1, 1);
        List<Long> list = new ArrayList<>();
        for (int i = 0; i < 100000; i++) {
            long id = snowFlakeId.nextId();
            list.add(id);
        }
        // 1040 4953 3655 5086 1
        list.forEach(System.out::println);
        HashSet<Long> set = new HashSet<>(list);
        System.out.println(set.size());
        System.out.println(Long.toBinaryString(System.currentTimeMillis()));
        System.out.println(Long.toBinaryString(System.currentTimeMillis() - BASE_TIMESTAMP));
    }
}
